/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import java.util.ArrayList;

import com.android.dx.dex.DexOptions;
import com.android.dx.io.opcode.Opcodes;
import com.android.dx.rop.code.BasicBlock;
import com.android.dx.rop.code.BasicBlockList;
import com.android.dx.rop.code.FillArrayDataInsn;
import com.android.dx.rop.code.Insn;
import com.android.dx.rop.code.LocalVariableInfo;
import com.android.dx.rop.code.PlainCstInsn;
import com.android.dx.rop.code.PlainInsn;
import com.android.dx.rop.code.RegOps;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.RegisterSpecSet;
import com.android.dx.rop.code.Rop;
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.SourcePosition;
import com.android.dx.rop.code.SwitchInsn;
import com.android.dx.rop.code.ThrowingCstInsn;
import com.android.dx.rop.code.ThrowingInsn;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstInteger;
import com.android.dx.util.Bits;
import com.android.dx.util.IntList;

/**
 * Translator from {@link RopMethod} to {@link DalvCode}. The {@link #translate}
 * method is the thing to call on this class.
 */
public final class RopTranslator {

	/** {@code non-null;} options for dex output */
	private final DexOptions dexOptions;

	/** {@code non-null;} method to translate */
	private final RopMethod method;

	/**
	 * how much position info to preserve; one of the static constants in
	 * {@link PositionList}
	 */
	private final int positionInfo;

	/** {@code null-ok;} local variable info to use */
	private final LocalVariableInfo locals;

	/** {@code non-null;} container for all the address objects for the method */
	private final BlockAddresses addresses;

	/** {@code non-null;} list of output instructions in-progress */
	private final OutputCollector output;

	/** {@code non-null;} visitor to use during translation */
	private final TranslationVisitor translationVisitor;

	/** {@code >= 0;} register count for the method */
	private final int regCount;

	/**
	 * {@code null-ok;} block output order; becomes non-null in
	 * {@link #pickOrder}
	 */
	private int[] order;

	/** size, in register units, of all the parameters to this method */
	private final int paramSize;

	/**
	 * true if the parameters to this method happen to be in proper order at the
	 * end of the frame (as the optimizer emits them)
	 */
	private boolean paramsAreInOrder;

	/**
	 * Translates a {@link RopMethod}. This may modify the given input.
	 * 
	 * @param method
	 *            {@code non-null;} the original method
	 * @param positionInfo
	 *            how much position info to preserve; one of the static
	 *            constants in {@link PositionList}
	 * @param locals
	 *            {@code null-ok;} local variable information to use
	 * @param paramSize
	 *            size, in register units, of all the parameters to this method
	 * @param dexOptions
	 *            {@code non-null;} options for dex output
	 * @return {@code non-null;} the translated version
	 */
	public static DalvCode translate(RopMethod method, int positionInfo,
			LocalVariableInfo locals, int paramSize, DexOptions dexOptions) {
		RopTranslator translator = new RopTranslator(method, positionInfo,
				locals, paramSize, dexOptions);
		return translator.translateAndGetResult();
	}

	/**
	 * Constructs an instance. This method is private. Use {@link #translate}.
	 * 
	 * @param method
	 *            {@code non-null;} the original method
	 * @param positionInfo
	 *            how much position info to preserve; one of the static
	 *            constants in {@link PositionList}
	 * @param locals
	 *            {@code null-ok;} local variable information to use
	 * @param paramSize
	 *            size, in register units, of all the parameters to this method
	 * @param dexOptions
	 *            {@code non-null;} options for dex output
	 */
	private RopTranslator(RopMethod method, int positionInfo,
			LocalVariableInfo locals, int paramSize, DexOptions dexOptions) {
		this.dexOptions = dexOptions;
		this.method = method;
		this.positionInfo = positionInfo;
		this.locals = locals;
		this.addresses = new BlockAddresses(method);
		this.paramSize = paramSize;
		this.order = null;
		this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize);

		BasicBlockList blocks = method.getBlocks();
		int bsz = blocks.size();

		/*
		 * Max possible instructions includes three code address objects per
		 * basic block (to the first and last instruction, and just past the end
		 * of the block), and the possibility of an extra goto at the end of
		 * each basic block.
		 */
		int maxInsns = (bsz * 3) + blocks.getInstructionCount();

		if (locals != null) {
			/*
			 * If we're tracking locals, then there's could be another extra
			 * instruction per block (for the locals state at the start of the
			 * block) as well as one for each interblock local introduction.
			 */
			maxInsns += bsz + locals.getAssignmentCount();
		}

		/*
		 * If params are not in order, we will need register space for them
		 * before this is all over...
		 */
		this.regCount = blocks.getRegCount()
				+ (paramsAreInOrder ? 0 : this.paramSize);

		this.output = new OutputCollector(dexOptions, maxInsns, bsz * 3,
				regCount);

		if (locals != null) {
			this.translationVisitor = new LocalVariableAwareTranslationVisitor(
					output, locals);
		} else {
			this.translationVisitor = new TranslationVisitor(output);
		}
	}

	/**
	 * Checks to see if the move-param instructions that occur in this method
	 * happen to slot the params in an order at the top of the stack frame that
	 * matches dalvik's calling conventions. This will alway result in "true"
	 * for methods that have run through the SSA optimizer.
	 * 
	 * @param paramSize
	 *            size, in register units, of all the parameters to this method
	 */
	private static boolean calculateParamsAreInOrder(RopMethod method,
			final int paramSize) {
		final boolean[] paramsAreInOrder = { true };
		final int initialRegCount = method.getBlocks().getRegCount();

		/*
		 * We almost could just check the first block here, but the {@code cf}
		 * layer will put in a second move-param in a subsequent block in the
		 * case of synchronized methods.
		 */
		method.getBlocks().forEachInsn(new Insn.BaseVisitor() {

			@Override
			public void visitPlainCstInsn(PlainCstInsn insn) {
				if (insn.getOpcode().getOpcode() == RegOps.MOVE_PARAM) {
					int param = ((CstInteger) insn.getConstant()).getValue();

					paramsAreInOrder[0] = paramsAreInOrder[0]
							&& ((initialRegCount - paramSize + param) == insn
									.getResult().getReg());
				}
			}
		});

		return paramsAreInOrder[0];
	}

	/**
	 * Does the translation and returns the result.
	 * 
	 * @return {@code non-null;} the result
	 */
	private DalvCode translateAndGetResult() {
		pickOrder();
		outputInstructions();

		StdCatchBuilder catches = new StdCatchBuilder(method, order, addresses);

		return new DalvCode(positionInfo, output.getFinisher(), catches);
	}

	/**
	 * Performs initial creation of output instructions based on the original
	 * blocks.
	 */
	private void outputInstructions() {
		BasicBlockList blocks = method.getBlocks();
		int[] order = this.order;
		int len = order.length;

		// Process the blocks in output order.
		for (int i = 0; i < len; i++) {
			int nextI = i + 1;
			int nextLabel = (nextI == order.length) ? -1 : order[nextI];
			outputBlock(blocks.labelToBlock(order[i]), nextLabel);
		}
	}

	/**
	 * Helper for {@link #outputInstructions}, which does the processing and
	 * output of one block.
	 * 
	 * @param block
	 *            {@code non-null;} the block to process and output
	 * @param nextLabel
	 *            {@code >= -1;} the next block that will be processed, or
	 *            {@code -1} if there is no next block
	 */
	private void outputBlock(BasicBlock block, int nextLabel) {
		// Append the code address for this block.
		CodeAddress startAddress = addresses.getStart(block);
		output.add(startAddress);

		// Append the local variable state for the block.
		if (locals != null) {
			RegisterSpecSet starts = locals.getStarts(block);
			output.add(new LocalSnapshot(startAddress.getPosition(), starts));
		}

		/*
		 * Choose and append an output instruction for each original
		 * instruction.
		 */
		translationVisitor.setBlock(block, addresses.getLast(block));
		block.getInsns().forEach(translationVisitor);

		// Insert the block end code address.
		output.add(addresses.getEnd(block));

		// Set up for end-of-block activities.

		int succ = block.getPrimarySuccessor();
		Insn lastInsn = block.getLastInsn();

		/*
		 * Check for (and possibly correct for) a non-optimal choice of which
		 * block will get output next.
		 */

		if ((succ >= 0) && (succ != nextLabel)) {
			/*
			 * The block has a "primary successor" and that primary successor
			 * isn't the next block to be output.
			 */
			Rop lastRop = lastInsn.getOpcode();
			if ((lastRop.getBranchingness() == Rop.BRANCH_IF)
					&& (block.getSecondarySuccessor() == nextLabel)) {
				/*
				 * The block ends with an "if" of some sort, and its secondary
				 * successor (the "then") is in fact the next block to output.
				 * So, reverse the sense of the test, so that we can just emit
				 * the next block without an interstitial goto.
				 */
				output.reverseBranch(1, addresses.getStart(succ));
			} else {
				/*
				 * Our only recourse is to add a goto here to get the flow to be
				 * correct.
				 */
				TargetInsn insn = new TargetInsn(Dops.GOTO,
						lastInsn.getPosition(), RegisterSpecList.EMPTY,
						addresses.getStart(succ));
				output.add(insn);
			}
		}
	}

	/**
	 * Picks an order for the blocks by doing "trace" analysis.
	 */
	private void pickOrder() {
		BasicBlockList blocks = method.getBlocks();
		int sz = blocks.size();
		int maxLabel = blocks.getMaxLabel();
		int[] workSet = Bits.makeBitSet(maxLabel);
		int[] tracebackSet = Bits.makeBitSet(maxLabel);

		for (int i = 0; i < sz; i++) {
			BasicBlock one = blocks.get(i);
			Bits.set(workSet, one.getLabel());
		}

		int[] order = new int[sz];
		int at = 0;

		/*
		 * Starting with the designated "first label" (that is, the first block
		 * of the method), add that label to the order, and then pick its first
		 * as-yet unordered successor to immediately follow it, giving top
		 * priority to the primary (aka default) successor (if any). Keep
		 * following successors until the trace runs out of possibilities. Then,
		 * continue by finding an unordered chain containing the first as-yet
		 * unordered block, and adding it to the order, and so on.
		 */
		for (int label = method.getFirstLabel(); label != -1; label = Bits
				.findFirst(workSet, 0)) {

			/*
			 * Attempt to trace backward from the chosen block to an as-yet
			 * unordered predecessor which lists the chosen block as its primary
			 * successor, and so on, until we fail to find such an unordered
			 * predecessor. Start the trace with that block. Note that the first
			 * block in the method has no predecessors, so in that case this
			 * loop will simply terminate with zero iterations and without
			 * picking a new starter block.
			 */
			traceBack: for (;;) {
				IntList preds = method.labelToPredecessors(label);
				int psz = preds.size();

				for (int i = 0; i < psz; i++) {
					int predLabel = preds.get(i);

					if (Bits.get(tracebackSet, predLabel)) {
						/*
						 * We found a predecessor loop; stop tracing back from
						 * here.
						 */
						break;
					}

					if (!Bits.get(workSet, predLabel)) {
						// This one's already ordered.
						continue;
					}

					BasicBlock pred = blocks.labelToBlock(predLabel);
					if (pred.getPrimarySuccessor() == label) {
						// Found one!
						label = predLabel;
						Bits.set(tracebackSet, label);
						continue traceBack;
					}
				}

				// Failed to find a better block to start the trace.
				break;
			}

			/*
			 * Trace a path from the chosen block to one of its unordered
			 * successors (hopefully the primary), and so on, until we run out
			 * of unordered successors.
			 */
			while (label != -1) {
				Bits.clear(workSet, label);
				Bits.clear(tracebackSet, label);
				order[at] = label;
				at++;

				BasicBlock one = blocks.labelToBlock(label);
				BasicBlock preferredBlock = blocks.preferredSuccessorOf(one);

				if (preferredBlock == null) {
					break;
				}

				int preferred = preferredBlock.getLabel();
				int primary = one.getPrimarySuccessor();

				if (Bits.get(workSet, preferred)) {
					/*
					 * Order the current block's preferred successor next, as it
					 * has yet to be scheduled.
					 */
					label = preferred;
				} else if ((primary != preferred) && (primary >= 0)
						&& Bits.get(workSet, primary)) {
					/*
					 * The primary is available, so use that.
					 */
					label = primary;
				} else {
					/*
					 * There's no obvious candidate, so pick the first one
					 * that's available, if any.
					 */
					IntList successors = one.getSuccessors();
					int ssz = successors.size();
					label = -1;
					for (int i = 0; i < ssz; i++) {
						int candidate = successors.get(i);
						if (Bits.get(workSet, candidate)) {
							label = candidate;
							break;
						}
					}
				}
			}
		}

		if (at != sz) {
			// There was a duplicate block label.
			throw new RuntimeException("shouldn't happen");
		}

		this.order = order;
	}

	/**
	 * Gets the complete register list (result and sources) out of a given rop
	 * instruction. For insns that are commutative, have two register sources,
	 * and have a source equal to the result, place that source first.
	 * 
	 * @param insn
	 *            {@code non-null;} instruction in question
	 * @return {@code non-null;} the instruction's complete register list
	 */
	private static RegisterSpecList getRegs(Insn insn) {
		return getRegs(insn, insn.getResult());
	}

	/**
	 * Gets the complete register list (result and sources) out of a given rop
	 * instruction. For insns that are commutative, have two register sources,
	 * and have a source equal to the result, place that source first.
	 * 
	 * @param insn
	 *            {@code non-null;} instruction in question
	 * @param resultReg
	 *            {@code null-ok;} the real result to use (ignore the insn's)
	 * @return {@code non-null;} the instruction's complete register list
	 */
	private static RegisterSpecList getRegs(Insn insn, RegisterSpec resultReg) {
		RegisterSpecList regs = insn.getSources();

		if (insn.getOpcode().isCommutative() && (regs.size() == 2)
				&& (resultReg.getReg() == regs.get(1).getReg())) {

			/*
			 * For commutative ops which have two register sources, if the
			 * second source is the same register as the result, swap the
			 * sources so that an opcode of form 12x can be selected instead of
			 * one of form 23x
			 */

			regs = RegisterSpecList.make(regs.get(1), regs.get(0));
		}

		if (resultReg == null) {
			return regs;
		}

		return regs.withFirst(resultReg);
	}

	/**
	 * Instruction visitor class for doing the instruction translation per se.
	 */
	private class TranslationVisitor implements Insn.Visitor {

		/** {@code non-null;} list of output instructions in-progress */
		private final OutputCollector output;

		/** {@code non-null;} basic block being worked on */
		private BasicBlock block;

		/**
		 * {@code null-ok;} code address for the salient last instruction of the
		 * block (used before switches and throwing instructions)
		 */
		private CodeAddress lastAddress;

		/**
		 * Constructs an instance.
		 * 
		 * @param output
		 *            {@code non-null;} destination for instruction output
		 */
		public TranslationVisitor(OutputCollector output) {
			this.output = output;
		}

		/**
		 * Sets the block currently being worked on.
		 * 
		 * @param block
		 *            {@code non-null;} the block
		 * @param lastAddress
		 *            {@code non-null;} code address for the salient last
		 *            instruction of the block
		 */
		public void setBlock(BasicBlock block, CodeAddress lastAddress) {
			this.block = block;
			this.lastAddress = lastAddress;
		}

		/** {@inheritDoc} */
		public void visitPlainInsn(PlainInsn insn) {
			Rop rop = insn.getOpcode();
			if (rop.getOpcode() == RegOps.MARK_LOCAL) {
				/*
				 * Ignore these. They're dealt with by the
				 * LocalVariableAwareTranslationVisitor
				 */
				return;
			}
			if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) {
				// These get skipped
				return;
			}

			SourcePosition pos = insn.getPosition();
			Dop opcode = RopToDop.dopFor(insn);
			DalvInsn di;

			switch (rop.getBranchingness()) {
			case Rop.BRANCH_NONE:
			case Rop.BRANCH_RETURN:
			case Rop.BRANCH_THROW: {
				di = new SimpleInsn(opcode, pos, getRegs(insn));
				break;
			}
			case Rop.BRANCH_GOTO: {
				/*
				 * Code in the main translation loop will emit a goto if
				 * necessary (if the branch isn't to the immediately subsequent
				 * block).
				 */
				return;
			}
			case Rop.BRANCH_IF: {
				int target = block.getSuccessors().get(1);
				di = new TargetInsn(opcode, pos, getRegs(insn),
						addresses.getStart(target));
				break;
			}
			default: {
				throw new RuntimeException("shouldn't happen");
			}
			}

			addOutput(di);
		}

		/** {@inheritDoc} */
		public void visitPlainCstInsn(PlainCstInsn insn) {
			SourcePosition pos = insn.getPosition();
			Dop opcode = RopToDop.dopFor(insn);
			Rop rop = insn.getOpcode();
			int ropOpcode = rop.getOpcode();
			DalvInsn di;

			if (rop.getBranchingness() != Rop.BRANCH_NONE) {
				throw new RuntimeException("shouldn't happen");
			}

			if (ropOpcode == RegOps.MOVE_PARAM) {
				if (!paramsAreInOrder) {
					/*
					 * Parameters are not in order at the top of the reg space.
					 * We need to add moves.
					 */

					RegisterSpec dest = insn.getResult();
					int param = ((CstInteger) insn.getConstant()).getValue();
					RegisterSpec source = RegisterSpec.make(regCount
							- paramSize + param, dest.getType());
					di = new SimpleInsn(opcode, pos, RegisterSpecList.make(
							dest, source));
					addOutput(di);
				}
			} else {
				// No moves required for the parameters
				RegisterSpecList regs = getRegs(insn);
				di = new CstInsn(opcode, pos, regs, insn.getConstant());
				addOutput(di);
			}
		}

		/** {@inheritDoc} */
		public void visitSwitchInsn(SwitchInsn insn) {
			SourcePosition pos = insn.getPosition();
			IntList cases = insn.getCases();
			IntList successors = block.getSuccessors();
			int casesSz = cases.size();
			int succSz = successors.size();
			int primarySuccessor = block.getPrimarySuccessor();

			/*
			 * Check the assumptions that the number of cases is one less than
			 * the number of successors and that the last successor in the list
			 * is the primary (in this case, the default). This test is here to
			 * guard against forgetting to change this code if the way switch
			 * instructions are constructed also gets changed.
			 */
			if ((casesSz != (succSz - 1))
					|| (primarySuccessor != successors.get(casesSz))) {
				throw new RuntimeException("shouldn't happen");
			}

			CodeAddress[] switchTargets = new CodeAddress[casesSz];

			for (int i = 0; i < casesSz; i++) {
				int label = successors.get(i);
				switchTargets[i] = addresses.getStart(label);
			}

			CodeAddress dataAddress = new CodeAddress(pos);
			SwitchData dataInsn = new SwitchData(pos, lastAddress, cases,
					switchTargets);
			Dop opcode = dataInsn.isPacked() ? Dops.PACKED_SWITCH
					: Dops.SPARSE_SWITCH;
			TargetInsn switchInsn = new TargetInsn(opcode, pos, getRegs(insn),
					dataAddress);

			addOutput(lastAddress);
			addOutput(switchInsn);

			addOutputSuffix(new OddSpacer(pos));
			addOutputSuffix(dataAddress);
			addOutputSuffix(dataInsn);
		}

		/**
		 * Looks forward to the current block's primary successor, returning the
		 * RegisterSpec of the result of the move-result-pseudo at the top of
		 * that block or null if none.
		 * 
		 * @return {@code null-ok;} result of move-result-pseudo at the
		 *         beginning of primary successor
		 */
		private RegisterSpec getNextMoveResultPseudo() {
			int label = block.getPrimarySuccessor();

			if (label < 0) {
				return null;
			}

			Insn insn = method.getBlocks().labelToBlock(label).getInsns()
					.get(0);

			if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) {
				return null;
			} else {
				return insn.getResult();
			}
		}

		/** {@inheritDoc} */
		public void visitThrowingCstInsn(ThrowingCstInsn insn) {
			SourcePosition pos = insn.getPosition();
			Dop opcode = RopToDop.dopFor(insn);
			Rop rop = insn.getOpcode();
			Constant cst = insn.getConstant();

			if (rop.getBranchingness() != Rop.BRANCH_THROW) {
				throw new RuntimeException("shouldn't happen");
			}

			addOutput(lastAddress);

			if (rop.isCallLike()) {
				RegisterSpecList regs = insn.getSources();
				DalvInsn di = new CstInsn(opcode, pos, regs, cst);

				addOutput(di);
			} else {
				RegisterSpec realResult = getNextMoveResultPseudo();

				RegisterSpecList regs = getRegs(insn, realResult);
				DalvInsn di;

				boolean hasResult = opcode.hasResult()
						|| (rop.getOpcode() == RegOps.CHECK_CAST);

				if (hasResult != (realResult != null)) {
					throw new RuntimeException(
							"Insn with result/move-result-pseudo mismatch "
									+ insn);
				}

				if ((rop.getOpcode() == RegOps.NEW_ARRAY)
						&& (opcode.getOpcode() != Opcodes.NEW_ARRAY)) {
					/*
					 * It's a type-specific new-array-<primitive>, and so it
					 * should be turned into a SimpleInsn (no constant ref as
					 * it's implicit).
					 */
					di = new SimpleInsn(opcode, pos, regs);
				} else {
					/*
					 * This is the general case for constant-bearing
					 * instructions.
					 */
					di = new CstInsn(opcode, pos, regs, cst);
				}

				addOutput(di);
			}
		}

		/** {@inheritDoc} */
		public void visitThrowingInsn(ThrowingInsn insn) {
			SourcePosition pos = insn.getPosition();
			Dop opcode = RopToDop.dopFor(insn);
			Rop rop = insn.getOpcode();
			RegisterSpec realResult;

			if (rop.getBranchingness() != Rop.BRANCH_THROW) {
				throw new RuntimeException("shouldn't happen");
			}

			realResult = getNextMoveResultPseudo();

			if (opcode.hasResult() != (realResult != null)) {
				throw new RuntimeException(
						"Insn with result/move-result-pseudo mismatch" + insn);
			}

			addOutput(lastAddress);

			DalvInsn di = new SimpleInsn(opcode, pos, getRegs(insn, realResult));

			addOutput(di);
		}

		/** {@inheritDoc} */
		public void visitFillArrayDataInsn(FillArrayDataInsn insn) {
			SourcePosition pos = insn.getPosition();
			Constant cst = insn.getConstant();
			ArrayList<Constant> values = insn.getInitValues();
			Rop rop = insn.getOpcode();

			if (rop.getBranchingness() != Rop.BRANCH_NONE) {
				throw new RuntimeException("shouldn't happen");
			}
			CodeAddress dataAddress = new CodeAddress(pos);
			ArrayData dataInsn = new ArrayData(pos, lastAddress, values, cst);

			TargetInsn fillArrayDataInsn = new TargetInsn(Dops.FILL_ARRAY_DATA,
					pos, getRegs(insn), dataAddress);

			addOutput(lastAddress);
			addOutput(fillArrayDataInsn);

			addOutputSuffix(new OddSpacer(pos));
			addOutputSuffix(dataAddress);
			addOutputSuffix(dataInsn);
		}

		/**
		 * Adds to the output.
		 * 
		 * @param insn
		 *            {@code non-null;} instruction to add
		 */
		protected void addOutput(DalvInsn insn) {
			output.add(insn);
		}

		/**
		 * Adds to the output suffix.
		 * 
		 * @param insn
		 *            {@code non-null;} instruction to add
		 */
		protected void addOutputSuffix(DalvInsn insn) {
			output.addSuffix(insn);
		}
	}

	/**
	 * Instruction visitor class for doing instruction translation with local
	 * variable tracking
	 */
	private class LocalVariableAwareTranslationVisitor extends
			TranslationVisitor {

		/** {@code non-null;} local variable info */
		private LocalVariableInfo locals;

		/**
		 * Constructs an instance.
		 * 
		 * @param output
		 *            {@code non-null;} destination for instruction output
		 * @param locals
		 *            {@code non-null;} the local variable info
		 */
		public LocalVariableAwareTranslationVisitor(OutputCollector output,
				LocalVariableInfo locals) {
			super(output);
			this.locals = locals;
		}

		/** {@inheritDoc} */
		@Override
		public void visitPlainInsn(PlainInsn insn) {
			super.visitPlainInsn(insn);
			addIntroductionIfNecessary(insn);
		}

		/** {@inheritDoc} */
		@Override
		public void visitPlainCstInsn(PlainCstInsn insn) {
			super.visitPlainCstInsn(insn);
			addIntroductionIfNecessary(insn);
		}

		/** {@inheritDoc} */
		@Override
		public void visitSwitchInsn(SwitchInsn insn) {
			super.visitSwitchInsn(insn);
			addIntroductionIfNecessary(insn);
		}

		/** {@inheritDoc} */
		@Override
		public void visitThrowingCstInsn(ThrowingCstInsn insn) {
			super.visitThrowingCstInsn(insn);
			addIntroductionIfNecessary(insn);
		}

		/** {@inheritDoc} */
		@Override
		public void visitThrowingInsn(ThrowingInsn insn) {
			super.visitThrowingInsn(insn);
			addIntroductionIfNecessary(insn);
		}

		/**
		 * Adds a {@link LocalStart} to the output if the given instruction in
		 * fact introduces a local variable.
		 * 
		 * @param insn
		 *            {@code non-null;} instruction in question
		 */
		public void addIntroductionIfNecessary(Insn insn) {
			RegisterSpec spec = locals.getAssignment(insn);

			if (spec != null) {
				addOutput(new LocalStart(insn.getPosition(), spec));
			}
		}
	}
}
